package com.googlecode.afdbaccess.config.propertyconfig;

import javax.sql.DataSource;

import org.apache.commons.dbcp.DataSourceConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.log4j.Logger;

import com.googlecode.afdbaccess.cache.connectionpool.interfaces.ConnectionPool;
import com.googlecode.afdbaccess.cache.connectionpool.interfaces.ConnectionPoolFactory;
import com.googlecode.afdbaccess.cache.exceptions.DataLayerException;

abstract public class DBCPPoolFactoryHelper implements ConnectionPoolFactory {
    private int defaultPort;
    private String validationQuery;

    protected String serverAddress;
    protected int serverPort;
    protected String serverDatabasename;
    protected String serverUser;
    protected String serverPassword;

    private static Logger log = Logger.getLogger(DBCPPoolFactoryHelper.class);

    public DBCPPoolFactoryHelper(int defaultPort, String validationQuery) {
        this.defaultPort = defaultPort;
        this.validationQuery = validationQuery;
    }

    @Override
    public ConnectionPool getNewConnectionPool() throws DataLayerException {
        return getNewConnectionPool(null);
    }

    @Override
    public ConnectionPool getNewConnectionPool(String root) throws DataLayerException {
        // Read params
        String rootString = "";
        if(root != null && root.trim().equals("") == false) {
            rootString = root + ".";
        }

        serverAddress = System.getProperty(rootString + "server-address");
        serverPort = ReadConfiguration.getIntSystemProperty(rootString + "server-port", defaultPort);
        serverDatabasename = System.getProperty(rootString + "server-databasename");
        serverUser = System.getProperty(rootString + "server-user");
        serverPassword = System.getProperty(rootString + "server-password");

        int cacheIdleTime = ReadConfiguration.getIntSystemProperty(rootString + "cache-idletime", 300);
        int cacheThreadWakeUpInterval = ReadConfiguration.getIntSystemProperty(rootString + "cache-threadwakeupinterval", 60);
        int cacheFixedWaitTimeOut = ReadConfiguration.getIntSystemProperty(rootString + "cache-fixedwaittimeout", -1);
        int cacheMaxLimit = ReadConfiguration.getIntSystemProperty(rootString + "cache-maxlimit", -1);

        log.debug(getParametersDebugString(cacheIdleTime, cacheThreadWakeUpInterval, cacheFixedWaitTimeOut, cacheMaxLimit));

        if (isParametersValid()) {
            throw new DataLayerException("Failed to read mandatory db parameters. Root: \"" + (root != null?root:"") + "\".");
        } else {
            return getNewConnectionPool(cacheIdleTime, cacheThreadWakeUpInterval, cacheFixedWaitTimeOut, cacheMaxLimit);
        }
    }

    protected boolean isParametersValid() {
        return serverAddress == null
            || serverDatabasename == null
            || serverUser == null
            || serverPassword == null;
    }

    protected String getParametersDebugString(int cacheIdleTime, int cacheThreadWakeUpInterval, int cacheFixedWaitTimeOut, int cacheMaxLimit) {
        return "Db parameters, values: \n"
            + getDataSourceParametersDebugString() + "\n"
            + getPoolParametersDebugString(cacheIdleTime, cacheThreadWakeUpInterval, cacheFixedWaitTimeOut, cacheMaxLimit);
    }
    
    protected String getDataSourceParametersDebugString() {
        return " server-address: " + serverAddress
            + "\n server-port: " + serverPort
            + "\n server-databasename: " + serverDatabasename
            + "\n server-user: " + serverUser
            + "\n server-password: " + serverPassword;
    }
    
    protected String getPoolParametersDebugString(int cacheIdleTime, int cacheThreadWakeUpInterval, int cacheFixedWaitTimeOut, int cacheMaxLimit) {
        return " cache-idletime: " + cacheIdleTime
            + "\n cache-threadwakeupinterval: " + cacheThreadWakeUpInterval
            + "\n cache-fixedwaittimeout: " + cacheFixedWaitTimeOut
            + "\n cacheMaxLimit: " + cacheMaxLimit;

    }

    protected ConnectionPool getNewConnectionPool(int cacheIdleTime, int cacheThreadWakeUpInterval, int cacheFixedWaitTimeOut, int cacheMaxLimit) throws DataLayerException {
        try {
            GenericObjectPool connectionPool = createConnectionPool(cacheIdleTime, cacheThreadWakeUpInterval, cacheFixedWaitTimeOut, cacheMaxLimit);

            DataSource dataSource = createDataSource();

            DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
            new PoolableConnectionFactory(connectionFactory, connectionPool, null, validationQuery, false,  true);
            PoolingDataSource poolingDataSource = new PoolingDataSource(connectionPool);

            return new DBCPPool(poolingDataSource, connectionPool);
        } catch (Exception exception) {
            throw new DataLayerException("Couldn't create connection cache!", exception);
        }
    }

    private GenericObjectPool createConnectionPool(int cacheIdleTime, int cacheThreadWakeUpInterval, int cacheFixedWaitTimeOut, int cacheMaxLimit) {
        GenericObjectPool connectionPool = new GenericObjectPool(null);
        connectionPool.setTestWhileIdle(true);
        connectionPool.setTestOnBorrow(true);
        connectionPool.setTestOnReturn(true);
        connectionPool.setTimeBetweenEvictionRunsMillis(cacheThreadWakeUpInterval * 1000);
        connectionPool.setMinEvictableIdleTimeMillis(cacheIdleTime * 1000);
        connectionPool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
        connectionPool.setMaxWait(cacheFixedWaitTimeOut * 1000);
        connectionPool.setMaxActive(cacheMaxLimit);
        connectionPool.setMaxIdle(cacheMaxLimit);
        return connectionPool;
    }

    abstract protected DataSource createDataSource() throws Exception;
}
